﻿namespace AjLogo.Compiler
{
    using System;
    using System.Collections.Generic;
    using System.Globalization;
    using System.IO;
    using System.Linq;
    using System.Text;

    public class Lexer
    {
        // TODO Review this data
        private const string Operators = "+-*/=<>";
        private const string Separators = "()[]";
        private const string ListSeparators = "[]";

        private static readonly string[] binaryOperators = { "<>", ">=", "<=" };

        private TextReader input;
        private char lastChar;
        private bool hasChar;
        private Token lastToken;
        private bool hasToken;

        private Token eol = CreateEoL();

        public Lexer(TextReader input)
        {
            this.input = input;
        }

        public Lexer(string input)
            : this(new StringReader(input))
        {
        }

        public Token NextToken()
        {
            if (this.hasToken)
            {
                this.hasToken = false;
                return this.lastToken;
            }

            char ch;

            try
            {
                ch = this.NextCharSkipBlanks();

                if (char.IsLetter(ch))
                    return this.NextName(ch);

                if (ch == '"')
                    return this.NextQuote();

                if (ch == ':')
                    return this.NextDots();

                if (ch == '\r' || ch == '\n')
                    return this.NextEoL(ch);

                if (char.IsDigit(ch))
                    return this.NextInteger(ch);

                if (Operators.IndexOf(ch) >= 0)
                    return this.NextOperator(ch);

                if (Separators.IndexOf(ch) >= 0)
                    return NextSeparator(ch);

                throw new LexerException("Invalid character '" + ch + "'");
            }
            catch (EndOfInputException)
            {
                return null;
            }
        }

        public Token NextListToken()
        {
            char ch;

            try
            {
                ch = this.NextCharSkipBlanks();

                if (ch == '[' || ch == ']')
                    return NextSeparator(ch);

                string name;

                name = new string(ch, 1);

                return this.GetListWord(name);
            }
            catch (EndOfInputException)
            {
                return null;
            }
        }

        internal void PushToken(Token token)
        {
            this.lastToken = token;
            this.hasToken = true;
        }

        private static Token NextSeparator(char ch)
        {
            Token token = new Token();
            token.TokenType = TokenType.Separator;
            token.Value = ch.ToString();
            return token;
        }

        private static Token CreateEoL()
        {
            return new Token() { TokenType = TokenType.EndOfLine, Value = string.Empty };
        }

        private void PushChar(char ch)
        {
            this.lastChar = ch;
            this.hasChar = true;
        }

        private char NextChar()
        {
            if (this.hasChar)
            {
                this.hasChar = false;
                return this.lastChar;
            }

            int ch;
            ch = this.input.Read();

            if (ch < 0)
            {
                throw new EndOfInputException();
            }

            return (char)ch;
        }

        private char NextCharSkipBlanks()
        {
            char ch;
            ch = this.NextChar();

            while (ch != '\r' && ch != '\n' && char.IsWhiteSpace(ch))
            {
                ch = this.NextChar();
            }

            return ch;
        }

        private string GetWord(string word)
        {
            char ch;
            try
            {
                ch = this.NextChar();

                while (!char.IsWhiteSpace(ch) && !Separators.Contains(ch) && !Operators.Contains(ch))
                {
                    if (ch == '\\')
                        ch = this.NextChar();

                    word += ch;
                    ch = this.NextChar();
                }

                this.PushChar(ch);
            }
            catch (EndOfInputException)
            {
            }

            return word;
        }

        private Token GetListWord(string word)
        {
            char ch;

            try
            {
                ch = this.NextChar();

                while (!char.IsWhiteSpace(ch) && !ListSeparators.Contains(ch))
                {
                    word += ch;
                    ch = this.NextChar();
                }

                this.PushChar(ch);
            }
            catch (EndOfInputException)
            {
            }

            Token token = new Token();
            token.Value = word;
            token.TokenType = TokenType.Name;

            return token;
        }

        private string GetQuotedWord()
        {
            string word = string.Empty;
            char ch;

            try
            {
                ch = this.NextChar();

                while (!char.IsWhiteSpace(ch) && !Separators.Contains(ch))
                {
                    if (ch == '\\')
                        ch = this.NextChar();

                    word += ch;
                    ch = this.NextChar();
                }

                this.PushChar(ch);
            }
            catch (EndOfInputException)
            {
            }

            return word;
        }

        private Token NextName(char firstChar)
        {
            string name;

            name = new string(firstChar, 1);

            name = this.GetWord(name);

            Token token = new Token();
            token.TokenType = TokenType.Name;
            token.Value = name;
            return token;
        }

        private Token NextQuote()
        {
            string value = this.GetQuotedWord();

            Token token = new Token();
            token.TokenType = TokenType.QuotedName;
            token.Value = value;
            return token;
        }

        private Token NextDots()
        {
            string value = this.GetQuotedWord();

            Token token = new Token();
            token.TokenType = TokenType.DotsName;
            token.Value = value;
            return token;
        }

        private Token NextInteger(char firstDigit)
        {
            string value;
            value = new string(firstDigit, 1);
            char ch;

            try
            {
                ch = this.NextChar();

                while (char.IsDigit(ch))
                {
                    value += ch;
                    ch = this.NextChar();
                }

                this.PushChar(ch);
            }
            catch (EndOfInputException)
            {
            }

            Token token = new Token();
            token.TokenType = TokenType.Integer;
            token.Value = System.Convert.ToInt32(value, CultureInfo.InvariantCulture);
            return token;
        }

        private Token NextEoL(char ch)
        {
            if (ch == '\r')
            {
                char ch2 = this.NextChar();

                if (ch2 != '\n')
                    this.PushChar(ch2);
            }

            return this.eol;
        }

        private Token NextOperator(char ch)
        {
            Token token = new Token();
            token.TokenType = TokenType.Operator;
            token.Value = ch.ToString();
            char ch2;
            try
            {
                ch2 = this.NextChar();
                if (Array.IndexOf(binaryOperators, ch + ch2) >= 0)
                {
                    token.Value = ch + ch2;
                }
                else
                {
                    this.PushChar(ch2);
                }
            }
            catch (EndOfInputException)
            {
            }

            return token;
        }
    }
}
